#!/usr/bin/env python
# -*- coding:utf-8 -*-

from copy import deepcopy
from collections import Counter, defaultdict
import json


def get_hierachical_kv(contents, key_list, default):
    default = deepcopy(default)
    for idx,key in enumerate(key_list):
        # contents需要一直是dict类型（除了最后一个key的结果），不然无法get
        if (not isinstance(contents, dict)) or (contents=={}):
            contents = default
            break
        contents = contents.get(key, default)
    # 最后一次get的结果需要不为None|''|[]|{}，否则以default返回
    if not contents:
        contents = default
    return contents


def set_hierachical_kv(contents, key_list, value):
    if key_list:
        cur_key = key_list[0]
        new_key_list = key_list[1:]
        if len(key_list) > 1:
            if contents.has_key(cur_key):
                contents[cur_key]=set_hierachical_kv(contents[cur_key], new_key_list, value)
            else:
                contents.update({cur_key:set_hierachical_kv({}, new_key_list, value)})
        else:
            if isinstance(contents, dict):
                contents[cur_key] = value
            else:
                contents = {cur_key: value}
    return contents


def trans_json(json_data, encoding='unicode'):
    if isinstance(json_data, list):
        json_data = map(lambda x:trans_json(x, encoding), json_data)
    elif isinstance(json_data, dict):
        json_data = {trans_json(k, encoding):trans_json(v, encoding) for k,v in json_data.iteritems()}
    elif isinstance(json_data, basestring):
        json_data = _decode(json_data) if encoding=='unicode' else _encode(_decode(json_data), encoding)
    return json_data


def _decode(data):
    if isinstance(data, str):
        for encoding in ['utf-8', 'gb18030', 'gb2312', 'gbk', 'big5']:
            try:
                return unicode(data, encoding)
            except:
                pass
    else:
        return data


def _encode(data, encoding='utf-8'):
    if isinstance(data, unicode):
        try:
            return data.encode(encoding)
        except:
            pass


def reduce_by_key(pair_list):
    result = []
    if pair_list:
        result = reduce(lambda x,y:Counter(x)+Counter(y), map(lambda (k,v):{k:v}, pair_list)).items()
    return result


def group_by_key(pair_list):
    d = defaultdict(list)
    for k, v in pair_list:
        d[k].append(v)
    return d.items()


def merge_data(d1, d2, num_rule='max', str_rule='maxlen', list_rule='replace'):
    ''' merge two data, data format should be the same!
    :param d1: update target
    :param d2: update from d2 data
    :param num_rule: max/min/boolexp, boolexp set the real bool expression
    :param str_rule: maxlen/minlen/boolexp, boolexp set the real bool expression
    :param list_rule: replace/add
    '''
    if (isinstance(d1, int) or isinstance(d1, float)) and (isinstance(d2, int) or isinstance(d2, float)):
        if num_rule == 'max': d1 = max(d1, d2)
        elif num_rule == 'min': d1 = min(d1, d2)
        elif num_rule == 'replace': d1 = d2
        elif num_rule: d1 = num_rule(d1, d2)
    elif isinstance(d1, basestring) and isinstance(d2, basestring):
        d1 = d1 if type(d1)==unicode else unicode(d1, 'utf-8')
        d2 = d2 if type(d2)==unicode else unicode(d2, 'utf-8')
        if str_rule == 'maxlen': d1 = d1 if len(d1)>len(d2) else d2
        elif str_rule == 'minlen': d1 = d1 if len(d1)<=len(d2) else d2
        elif str_rule == 'replace': d1 = d2
        elif str_rule: d1 = str_rule(d1, d2)
    elif isinstance(d1, dict) and isinstance(d2, dict):
        for k,v in d2.items():
            if d1.has_key(k):
                d1[k] = merge_data(d1[k], v, num_rule=num_rule, str_rule=str_rule, list_rule=list_rule)
            else:
                d1.update({k:v})
    elif (type(d1)==list or isinstance(d1, tuple)) and ((type(d2)==list or isinstance(d2, tuple))):
        if list_rule=='replace' and len(json.dumps(d1))<len(json.dumps(d2)):
            d1 = d2
        elif list_rule=='add':
            d1 = list(d1)
            for item in d2:
                if item not in d1:
                    d1.append(item)

    return d1 or d2
